package com.general.framework.web.aop;

import com.general.framework.core.enums.LimitType;
import com.general.framework.core.exception.CodeEnum;
import com.general.framework.core.exception.Ex;
import com.general.framework.web.annotations.RateLimiter;
import com.general.framework.web.utils.IpUtils;
import com.general.framework.web.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * 限流处理
 * @author leejean
 */
@Aspect
@Component
@Slf4j
public class RateLimiterAspect {

    private RedisTemplate<Object, Object> redisTemplate;

    private RedisScript<Number> limitScript;

    @Resource
    public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Resource
    public void setLimitScript(RedisScript<Number> limitScript) {
        this.limitScript = limitScript;
    }

    // 配置织入点
    @Pointcut("@annotation(com.general.framework.web.annotations.RateLimiter)")
    public void rateLimiterPointCut() {
    }

    @Before("rateLimiterPointCut()")
    public void doBefore(JoinPoint point) {
        RateLimiter rateLimiter = getAnnotationRateLimiter(point);
        if (Objects.isNull(rateLimiter)) {
            return;
        }
        int time = rateLimiter.time();
        int count = rateLimiter.count();
        String combineKey = getCombineKey(rateLimiter, point);
        List<Object> keys = Collections.singletonList(combineKey);
        Number number = redisTemplate.execute(limitScript, keys, count, time);
        log.debug("限制请求:{},当前请求:{},缓存key:{}", count, number, combineKey);
        if (Objects.isNull(number) || number.intValue() > count) {
            throw Ex.business(CodeEnum.TRY_AGAIN_LATER.getCode(), rateLimiter.message());
        }
    }

    /**
     * 是否存在注解，如果存在就获取
     */
    private RateLimiter getAnnotationRateLimiter(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null) {
            return method.getAnnotation(RateLimiter.class);
        }
        return null;
    }

    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
        // 这里使用StringBuffer线程安全
        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
        if (rateLimiter.limitType() == LimitType.IP) {
            stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest()));
        }
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        stringBuffer.append("-").append(targetClass.getName()).append("- ").append(method.getName());
        return stringBuffer.toString();
    }
}
